package parser.nodes;

import java.util.Collections;
import codegen.*;
import java.util.ArrayList;
import java.util.List;
import parser.Pair;
import scanner.Token;
import static scanner.TokenType.*;

// Siehe Folie
//Constdecls abgeändert, sodass diese nur noch integer, oder integerconstanten akzeptieren
public class DeclarationsNode extends Node {

    private List<Pair<Token, Token>> consts = new ArrayList<>();
    private List<Pair<Token, TypeNode>> types = new ArrayList<>();
    private List<Pair<IdentListNode, TypeNode>> vars = new ArrayList<>();
    private List<ProcedureDeclarationNode> procDecls = new ArrayList<>();

    public void addConstant(Token ident, Token constExpr) {
        consts.add(new Pair<>(ident, constExpr));
    }

    public void addType(Token ident, TypeNode type) {
        types.add(new Pair<>(ident, type));
    }

    public void addVar(IdentListNode idents, TypeNode type) {
        vars.add(new Pair<>(idents, type));
    }

    public void addProcDecl(ProcedureDeclarationNode decl) {
        procDecls.add(decl);
    }

    @Override
    public void print() {
        p("Declarations:");
        indent();
        if (!consts.isEmpty()) {
            p("CONST");
            for (Pair<Token, Token> cdecl : consts) {
                p(cdecl.getFirst() + " = " + cdecl.getSecond() + ";");
            }
        }
        if (!types.isEmpty()) {
            p("TYPE");
            for (Pair<Token, TypeNode> tdecl : types) {
                p(tdecl.getFirst() + " =");
                tdecl.getSecond().print();
                p(";");
            }
        }
        if (!vars.isEmpty()) {
            p("TYPE");
            for (Pair<IdentListNode, TypeNode> vdecl : vars) {
                vdecl.getFirst().print();
                p("=");
                vdecl.getSecond().print();
                p(";");
            }
        }
        for (ProcedureDeclarationNode pdecl : procDecls) {
            pdecl.print();
            p(";");
        }
        unindent();
    }

    @Override
    public void abstractPrint() {
        p("DECLARATIONS");
        indent();
        if (!consts.isEmpty()) {
            p("CONST");
            indent();
            for (Pair<Token, Token> cdecl : consts) {
                p(cdecl.getFirst());
                indent();
                p(cdecl.getSecond());
                unindent();
            }
            unindent();
        }
        if (!types.isEmpty()) {
            p("TYPE");
            indent();
            for (Pair<Token, TypeNode> tdecl : types) {
                p(tdecl.getFirst());
                indent();
                tdecl.getSecond().abstractPrint();
                unindent();
            }
            unindent();
        }
        if (!vars.isEmpty()) {
            p("VAR");
            indent();
            for (Pair<IdentListNode, TypeNode> vdecl : vars) {
                vdecl.getFirst().abstractPrint();
                indent();
                vdecl.getSecond().abstractPrint();
                unindent();
            }
            unindent();
        }
        for (ProcedureDeclarationNode pdecl : procDecls) {
            p("PROCEDURES");
            indent();
            pdecl.abstractPrint();
            unindent();
        }

        unindent();
    }

    @Override
    public void compile(CodeGen c) throws CompileError {

        for (Pair<Token, Token> cdecl : consts) {
            int value = 0;
            Token constIdent = cdecl.getFirst();
            Token val = cdecl.getSecond();
            if (val.type() == IDENTIFIER) {
                AbstractDescr constant = c.findDescr(val.toString());
                if (constant instanceof ConstDescr) {
                    value = ((ConstDescr) constant).getValue();
                } else {
                    cerr("The constant '" + constIdent + "' at " + constIdent.line() + ":" + constIdent.col() + " cannot be declared with non-constant " + val);
                }
            } else if (val.type() == INTEGER) {
                value = Integer.parseInt(val.toString());
            } else {
                cerr("The constant '" + constIdent + "' at " + constIdent.line() + ":" + constIdent.col() + " cannot be declared with " + val + ". Expected an integer, or constant");
            }
            c.putDescr(cdecl.getFirst().toString(), new ConstDescr(value));
        }

        for (Pair<Token, TypeNode> tdecl : types) {
            c.putDescr(tdecl.getFirst().toString(), new TypeDescr(tdecl.getFirst().toString(), tdecl.getSecond().getDescriptor(c)));
        }

        for (Pair<IdentListNode, TypeNode> vdecl : vars) {
            if (vdecl.getSecond().getType().Token() != null) { //Basisdatentyp
                String typename = vdecl.getSecond().getType().Token().toString();
                AbstractDescr type = c.findDescr(typename);
                if (type != null) {
                    List<Token> names = vdecl.getFirst().getIdentList();
                    for (Token name : names) {
                        c.putDescr(name.toString(), new VarDescr(c.nextFreeAddress(), type, c.getLevel()));
                    }
                } else {
                    System.err.println("Ungütiger Typ: " + typename);
                }
            } else { //Array oder Record
                Node node = vdecl.getSecond().getType().Node();
                if (node instanceof ArrayTypeNode) {
                    ArrayTypeNode array = (ArrayTypeNode) node;
                    List<Token> names = vdecl.getFirst().getIdentList();
                    for (Token name : names) {
                        c.putDescr(name.toString(), new VarDescr(c.nextFreeAddress(), array.getDescriptor(c), c.getLevel()));
                    }
                } else { //Record
                    RecordTypeNode record = (RecordTypeNode) node;
                    List<Token> names = vdecl.getFirst().getIdentList();
                    for (Token name : names) {
                        c.putDescr(name.toString(), new VarDescr(c.nextFreeAddress(), record.getDescriptor(c), c.getLevel()));
                    }
                }
            }
        }

        preparse(c);
        for (ProcedureDeclarationNode proc : procDecls) {
            proc.compile(c); //Mehr ist hier nicht zu tun... alles sollte wunderbar funktionieren, wenn preparse richtig arbeitet
        }
    }

    /**
     * Diese Methode weist schonmal jeder Prozedur eine Startmarke und einen
     * Symboltabelleneintrag zu. Die Level werden wie folgt genutzt. >
     * ParamListe < > Declarations <
     *
     * Dadurch sind die ParameterListe und die lokalen Deklarationen auf dem
     * gleichen level. Beim späteren parsen der Deklarationen muss zuerst ein
     * neues Level geöffnet werden und alle Deskriptoren aus der ParamListe dort
     * eingetragen werden. (Um zu vermeiden, dass lokal definierte Variablen den
     * gleichen Namen tragen, wie Parameter)
     */
    private void preparse(CodeGen c) throws CompileError {
        for (ProcedureDeclarationNode proc : procDecls) {
            ProcedureHeadingNode signature = proc.getSignature();
            int initLabel = c.getLabel();
            String procname = signature.getProcName().toString();
            List<ParamDescr> params = new ArrayList<>();
            c.levelUp();
            for (FPSectionNode pSection : signature.getParams()) {
                for (ParamDescr param : pSection.getParamDescrList(c)) {
                    params.add(param);
                }
            }
            c.levelDown(); //Symboltabelle kann einfach verworfen werden, da wir die Deskriptoren in einer List haben

            c.putDescr(procname, new ProcedureDescr(procname, initLabel, params));
        }
    }
}
